/*
    json.c.tst - Unit tests for JSON primitives

    Copyright (c) All Rights Reserved. See details at the end of the file.
 */

/********************************** Includes **********************************/

#include    "testme.h"
#include    "mpr.h"

/************************************ Code ************************************/

static void jsonEmpty()
{
    MprJson *obj;
    char    *input, *s;

    input = 0;
    obj = mprParseJson(input);
    ttrue(obj == 0);

    input = "";
    obj = mprParseJson(input);
    ttrue(obj == 0);

    s = mprJsonToString(0, 0);
    ttrue(s == 0);
}


static void jsonConstruct()
{
    MprJson *obj;
    char    *input, *s;

    input = "{}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_OBJ);
    ttrue(mprGetJsonLength(obj) == 0);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{}"));

    input = "[]";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_ARRAY);
    ttrue(mprGetJsonLength(obj) == 0);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "[]"));

    input = "{a:42}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_OBJ);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{a:42}"));

    input = "[1,2,3]";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_ARRAY);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "[1,2,3]"));

    /*
        Strings must be objects {} or arrays []
     */
    obj = mprParseJson("1");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_NUMBER);

    obj = mprParseJson("true");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_TRUE);
    ttrue(smatch(obj->value, "true"));

    obj = mprParseJson("false");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_FALSE);
    ttrue(smatch(obj->value, "false"));

    obj = mprParseJson("null");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_NULL);

    obj = mprParseJson("undefined");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_UNDEFINED);
    ttrue(smatch(obj->value, "undefined"));

#if FUTURE
    obj = mprParseJson("{ value: /pattern/");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_REGEXP);
    ttrue(smatch(obj->value, "/pattern/"));
#endif
    
    obj = mprParseJson("'hello world'");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_STRING);
    ttrue(smatch(obj->value, "hello world"));

    obj = mprParseJson("5.42");
    ttrue(obj != 0);
    ttrue(obj->type & MPR_JSON_VALUE);
    ttrue(obj->type & MPR_JSON_NUMBER);
    ttrue(smatch(obj->value, "5.42"));
}


static void jsonStrings()
{
    MprJson *obj;
    char    *s;

    obj = mprParseJson("{ 1234: 42 }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{1234:42}"));

    obj = mprParseJson("{ \"abc\\u0001def\": 42 }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{\"abc\\u0001def\":42}"));

    obj = mprParseJson("{\"one\ttwo\": \"three\tfour\"}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{\"one\\ttwo\":\"three\\tfour\"}"));

    obj = mprParseJson("{\"one\n\": \"two\r\"}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{\"one\\n\":\"two\\r\"}"));

    obj = mprParseJson("{\"one\\u0001\": \"two\"}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{\"one\\u0001\":\"two\"}"));
}


static void jsonTypes()
{
    MprJson *obj;
    char    *s;

    obj = mprParseJson("{ value: 'hello' }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{ value: \"hello\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{ value: true }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:true}"));

    obj = mprParseJson("{ value: false }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:false}"));

    obj = mprParseJson("{ value: 42 }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:42}"));

    obj = mprParseJson("{ value: 3.14159 }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:3.14159}"));

    obj = mprParseJson("{ value: null }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:null}"));

    obj = mprParseJson("{ value: undefined }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:undefined}"));

#if FUTURE
    obj = mprParseJson("{ value: /abc/ }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:/abc/}"));
#endif

    /* 
        Special values as strings 
    */
    obj = mprParseJson("{ value: \"undefined\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"undefined\"}"));

    obj = mprParseJson("{ value: \"null\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"null\"}"));

    obj = mprParseJson("{ value: \"1.2\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"1.2\"}"));

    obj = mprParseJson("{ value: \"true\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"true\"}"));

    obj = mprParseJson("{ value: \"false\" }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"false\"}"));
}


static void jsonWhite()
{
    MprJson *obj;
    char    *s;

    obj = mprParseJson("{value:'hello'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("     {value:'hello'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{value:'hello'}       ");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{     value:'hello'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{value      :'hello'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{value:        'hello'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));

    obj = mprParseJson("{value:'hello'        }");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"hello\"}"));
}


static void jsonReadWrite()
{ 
    MprJson     *obj;

    obj = mprParseJson("{value:'abc'}");
    ttrue(smatch(mprReadJson(obj, "value"), "abc"));

    obj = mprParseJson("{value:'abc'}");
    ttrue(mprWriteJson(obj, "value", "def", 0) == 0);
    ttrue(smatch(mprReadJson(obj, "value"), "def"));

    //  Should never do this in practice, but verifying that dotted keys can be read/written
    obj = mprParseJson("{'a.value':'abc'}");
    ttrue(smatch(mprReadJson(obj, "a.value"), "abc"));
    ttrue(mprWriteJson(obj, "value", "def", 0) == 0);
    ttrue(smatch(mprReadJson(obj, "a.value"), "abc"));
    ttrue(smatch(mprReadJson(obj, "value"), "def"));
}


static void jsonToString()
{
    MprJson     *obj;
    cchar       *s;

    s = mprJsonToString(0, 0);
    ttrue(s == 0);

    obj = mprParseJson("{}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{}"));

    obj = mprParseJson("[]");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "[]"));

    obj = mprParseJson("42");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "42"));

    obj = mprParseJson("{age:42}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{age:42}"));

    //  Simple string
    obj = mprParseJson("{value:'abc'}");
    ttrue(smatch(mprGetJson(obj, "value"), "abc"));
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"abc\"}"));

    //  Embedded quotes
    obj = mprParseJson("{value:'\"abc\"'}");
    ttrue(smatch(mprGetJson(obj, "value"), "\"abc\""));
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"\\\"abc\\\"\"}"));

    //  Embedded backslash
    //  This will embed a single "\" in the actual property value.
    obj = mprParseJson("{value:'ab\\\\c'}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(mprGetJson(obj, "value"), "ab\\c"));
    ttrue(smatch(s, "{value:\"ab\\\\c\"}"));

    //  Backslash at the end
    s = "{value:\"abc\\\\\"}";
    obj = mprParseJson(s);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{value:\"abc\\\\\"}"));
}


static void jsonPretty()
{
    MprJson *obj;
    char    *input, *s;

    input = "\
{\n\
    shape: {\n\
        color: 'red',\n\
        width: 42,\n\
        visible: true,\n\
        extends: null,\n\
        shading: [1, 7, 14, 23],\n\
    }\n\
}\n";
    obj = mprParseJson(input);
    s = mprJsonToString(obj, MPR_JSON_PRETTY);
    ttrue(scontains(s, "shape: {") != 0);
    ttrue(scontains(s, "    color: \"red\"") != 0);
    ttrue(scontains(s, "    shading: [") != 0);
    ttrue(scontains(s, "    extends: null") != 0);
    ttrue(scontains(s, "    width: 42") != 0);
}


static void jsonRemove()
{
    MprJson *obj, *removed;
    char    *input;
    int     rc;

    obj = mprParseJson("{}");
    rc = mprSetJson(obj, "number", "42", MPR_JSON_NUMBER);
    ttrue(rc == 0);
    ttrue(mprGetJsonLength(obj) == 1);
    ttrue(smatch(mprGetJson(obj, "number"), "42"));
    removed = mprRemoveJson(obj, "number");
    ttrue(rc == 0);
    ttrue(mprGetJsonLength(removed) == 1);
    ttrue(mprGetJsonLength(obj) == 0);
    ttrue(mprGetJsonObj(obj, "number") == 0);

    obj = mprParseJson("{}");
    removed = mprRemoveJson(obj, "not-here");
    ttrue(mprGetJsonLength(removed) == 0);

    input = "\
{\n\
    info: {\n\
        users: [\n\
            {\n\
                name: 'mary',\n\
                rank: 1,\n\
            },\n\
            {\n\
                name: 'john',\n\
                rank: 2,\n\
            },\n\
        ],\n\
        updated: 'today',\n\
        colors: ['red', 'white', 'blue', 'yellow']\n\
    }\n\
}\n";
    obj = mprParseJson(input);
    mprRemoveJson(obj, "info.users[1]");
    ttrue(!scontains(mprJsonToString(obj, 0), "john"));

    obj = mprParseJson(input);
    mprRemoveJson(obj, "info.colors[@='red']");
    ttrue(!scontains(mprJsonToString(obj, 0), "red"));

    obj = mprParseJson(input);
    mprRemoveJson(obj, "info.colors[1:2]");
    ttrue(!scontains(mprJsonToString(obj, 0), "[\"red\",\"yellow\"]"));

    obj = mprParseJson(input);
    mprRemoveJson(obj, "info.users[name = 'mary']");
    ttrue(!scontains(mprJsonToString(obj, 0), "mary"));
}


static void jsonQuery()
{
    MprJson *obj, *set, *result;
    cchar   *input, *s;

    input = "\
{\n\
    info: {\n\
        users: [\n\
            {\n\
                name: 'mary',\n\
                rank: 1,\n\
            },\n\
            {\n\
                name: 'john',\n\
                rank: 2,\n\
            },\n\
        ],\n\
        updated: 'today',\n\
        colors: ['red', 'white', 'blue'],\n\
        options: { bright: true, loud: false},\n\
    }\n\
}\n";
    obj = mprParseJson(input);
    s = mprGetJson(obj, "info.updated");
    ttrue(smatch(s, "today"));

    obj = mprParseJson(input);
    result = mprGetJsonObj(obj, "info.updated");
    ttrue(result && result->type & MPR_JSON_STRING);

    s = mprGetJson(obj, "info.colors[0]");
    ttrue(smatch(s, "red"));
    s = mprGetJson(obj, "info.users[0].name");
    ttrue(smatch(s, "mary"));

    s = mprGetJson(obj, "info.users[name = 'mary'].rank");
    ttrue(smatch(s, "1"));

    s = mprGetJson(obj, "info.colors[@ = 'red']");
    ttrue(smatch(s, "red"));

    s = mprGetJson(obj, "info.options[@ = 'nothing']");
    ttrue(s == 0);

    set = mprQueryJson(obj, "info.colors[0:1]", 0, 0);
    ttrue(smatch(mprJsonToString(set, 0), "[\"red\",\"white\"]"));

    set = mprQueryJson(obj, "info.colors[-2:-1]", 0, 0);
    ttrue(smatch(mprJsonToString(set, 0), "[\"white\",\"blue\"]"));

    /* Pattern match */
    set = mprQueryJson(obj, "info.colors[@ ~ 'ite']", 0, 0);
    s = mprJsonToString(set, 0);
    ttrue(smatch(s, "[\"white\"]"));

    set = mprQueryJson(obj, "info.users[ name ~ 'mary'].name", 0, 0);
    s = mprJsonToString(set, 0);
    ttrue(smatch(s, "[\"mary\"]"));

    set = mprQueryJson(obj, "info.colors[@ !~ 'ite']", 0, 0);
    s = mprJsonToString(set, 0);
    ttrue(smatch(s, "[\"red\",\"blue\"]")); 

    set = mprQueryJson(obj, "info.colors[@ != 'white']", 0, 0);
    s = mprJsonToString(set, 0);
    ttrue(smatch(s, "[\"red\",\"blue\"]")); 

    //  Expression should not parse
    set = mprQueryJson(obj, "info.users[name !<]", 0, 0);
    s = mprJsonToString(set, 0);
    ttrue(smatch(s, "[]")); 
}


static void jsonSelectors()
{
    MprJson *obj;
    cchar   *input, *s;
    int     rc;

    //  Object get selectors . [] and '*' property names
    input = "{john: { age: 42}, peter: {age: 23}}";
    obj = mprParseJson(input);
    s = mprGetJson(obj, "john.age");
    ttrue(smatch(s, "42"));
    s = mprGetJson(obj, "john[age]");
    ttrue(smatch(s, "42"));
    s = mprGetJson(obj, "john.*");
    ttrue(smatch(s, "42"));
    s = mprGetJson(obj, "john[*]");
    ttrue(smatch(s, "42"));

    //  Array get selectors
    input = "{users: [{name: 'john', age:42}, {name: 'peter', age:23}]}";
    obj = mprParseJson(input);
    s = mprGetJson(obj, "users[0].age");
    ttrue(smatch(s, "42"));

    s = mprGetJson(obj, "users.0.age");
    ttrue(smatch(s, "42"));

    s = mprGetJson(obj, "users[*].age");
    ttrue(smatch(s, "[42,23]"));

    s = mprGetJson(obj, "users.*.age");
    ttrue(smatch(s, "[42,23]"));
    
    s = mprGetJson(obj, "users[1:1].age");
    ttrue(smatch(s, "23"));

    //  Set 
    input = "{john: { age: 42}, peter: {age: 23}}";
    obj = mprParseJson(input);
    rc = mprSetJson(obj, "john.age", "43", MPR_JSON_NUMBER);
    ttrue(rc == 0);
    s = mprGetJson(obj, "john.age");
    ttrue(smatch(s, "43"));

    rc = mprSetJson(obj, "john[age]", "44", MPR_JSON_NUMBER);
    s = mprGetJson(obj, "john.age");
    ttrue(smatch(s, "44"));

    rc = mprSetJson(obj, "john.*", "45", MPR_JSON_NUMBER);
    s = mprGetJson(obj, "john.*");
    ttrue(smatch(s, "45"));
    
    rc = mprSetJson(obj, "john[*]", "46", MPR_JSON_NUMBER);
    s = mprGetJson(obj, "john.*");
    ttrue(smatch(s, "46"));

    //  Append new element
    input = "[ 1, 2, 3 ]";
    obj = mprParseJson(input);
    s = mprJsonToString(obj, 0);
    rc = mprSetJson(obj, "[$]", "4", MPR_JSON_NUMBER);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "[1,2,3,4]"));

    //  Set with wildcards
    input = "{users: [{name: 'john', age:42}, {name: 'peter', age:23}]}";
    obj = mprParseJson(input);
    rc = mprSetJson(obj, "users[*].age", "43", MPR_JSON_NUMBER);
    s = mprGetJson(obj, "users[0].age");
    ttrue(smatch(s, "43"));
    s = mprGetJson(obj, "users[1].age");
    ttrue(smatch(s, "43"));
}


static void jsonElipsis()
{
    MprJson     *obj;
    cchar       *input, *s;

    input = "\
{\n\
    info: {\n\
        users: [\n\
            {\n\
                name: 'mary',\n\
                rank: 1,\n\
                extra: {\n\
                    age: 23\n\
                }\n\
            },\n\
            {\n\
                name: 'john',\n\
                rank: 2,\n\
                extra: {\n\
                    age: 33\n\
                }\n\
            },\n\
        ],\n\
    }\n\
}\n";
    obj = mprParseJson(input);
    s = mprGetJson(obj, "info..rank");
    ttrue(smatch(s, "[1,2]"));

    obj = mprParseJson(input);
    s = mprGetJson(obj, "info..rank");
    ttrue(smatch(s, "[1,2]"));

    obj = mprParseJson(input);
    s = mprGetJson(obj, "info..extra.age");
    ttrue(smatch(s, "[23,33]"));
}


static void jsonSet()
{
    MprJson *obj, *set;
    cchar   *s;
    int     rc;

    obj = mprParseJson("{}");
    rc = mprSetJson(obj, "color", "red", 0);
    ttrue(rc == 0);
    ttrue(smatch(mprGetJson(obj, "color"), "red"));

    obj = mprParseJson("{}");
    rc = mprSetJson(obj, "number", "42", 0);
    ttrue(rc == 0);
    ttrue(smatch(mprGetJson(obj, "number"), "42"));

    /* Test update of existing value */
    rc = mprSetJson(obj, "number", "43", 0);
    ttrue(rc == 0);
    ttrue(smatch(mprGetJson(obj, "number"), "43"));

    obj = mprParseJson("{ user: { name: 'john' }}");
    rc = mprSetJson(obj, "user.rank", "42", 0);
    ttrue(rc == 0);
    ttrue(smatch(mprGetJson(obj, "user.rank"), "42"));

    /*
        Add element to array
     */
    obj = mprParseJson("{}");
    rc = mprSetJson(obj, "abc[$]", "fred", 0);
    ttrue(rc == 0);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{abc:[\"fred\"]}"));
    
    obj = mprParseJson("{users: []}");
    rc = mprSetJson(obj, "users[$]", "fred", 0);
    ttrue(rc == 0);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(mprGetJson(obj, "users[0]"), "fred"));

    //  Add an array to an object
    obj = mprParseJson("{}");
    set = mprParseJson("[1, 2, 3]");
    rc = mprSetJsonObj(obj, "number", set);
    ttrue(rc == 0);
    ttrue(smatch(mprJsonToString(mprGetJsonObj(obj, "number"), 0), "[1,2,3]"));

    //  Properties with embedded "."
    obj = mprParseJson("{ \"a.b.c\": 42}");
    ttrue(mprGetJsonObj(obj, "a.b.c") == 0);
    ttrue(smatch(mprJsonToString(mprReadJsonObj(obj, "a.b.c"), 0), "42"));

    //  Set with object
    obj = mprParseJson("{users: [{name: mary}]}");
    mprSetJsonObj(obj, "users[name = 'mary']", mprParseJson("{name:'mary', age: 33}"));
    s = mprJsonToString(obj, 0);
    ttrue(smatch(mprGetJson(obj, "users[name = 'mary'].age"), "33"));

    obj = mprParseJson("{}");
    mprSetJson(obj, 0, "hello", 0);
}


static void blend(cchar *dest, cchar *src, cchar *result, int flags)
{
    MprJson     *dp, *sp;
    cchar       *str;
    int         rc;

    dp = mprParseJson(dest);
    sp = mprParseJson(src);
    rc = mprBlendJson(dp, sp, flags);
    ttrue(rc == 0);
    str = mprJsonToString(dp, 0);
    ttrue(smatch(str, result));
}


static void jsonBlend()
{
    /*
        Note: default is to append object properties and to treat arrays like primitive types and overwrite them
     */
    blend("{}", 0, "{}", 0);
    blend("{}", "{}", "{}", 0);

    blend("{}", "{enable:true}", "{enable:true}", 0);

    blend("[]", 0, "[]", 0);
    blend("[]", "[]", "[]", 0);
    blend("[1,2]", "[]", "[]", 0);
    blend("[]", "[1,2]", "[1,2]", 0);
    blend("[1,2]", "[3,4]", "[3,4]", 0);
    blend("[1,2]", "[3,4]", "[1,2,3,4]", MPR_JSON_APPEND);

    blend("{numbers:[1,2]}", "{numbers:[3,4]}", "{numbers:[3,4]}", 0);
    blend("{}", "{user:{name:'john',age:42}}", "{user:{name:\"john\",age:42}}", 0);
    blend("{user:{name:'john'}}", "{user:{age:42}}", "{user:{name:\"john\",age:42}}", 0);

    blend("{enable:true}", "{enable:false}", "{enable:true}", MPR_JSON_CREATE);
    blend("{enable:true}", "{enable:false}", "{enable:false}", 0);
    blend("{user:{}}", "{user:{name:'john'}}", "{user:{name:\"john\"}}", 0);

    blend("{enable:true}", "{enable:false}", "{enable:false}", MPR_JSON_OVERWRITE);
    blend("{enable:true}", "{enable:{color:'blue'}}", "{enable:{color:\"blue\"}}", MPR_JSON_OVERWRITE);

    /*
        Combine prefixes
     */
    blend("{}", "{numbers:[1,2]}", "{numbers:[1,2]}", MPR_JSON_COMBINE);
    blend("{}", "{-numbers:[1,2]}", "{}", MPR_JSON_COMBINE);
    blend("{}", "{=numbers:[1,2]}", "{numbers:[1,2]}", MPR_JSON_COMBINE);
    blend("{}", "{?numbers:[1,2]}", "{numbers:[1,2]}", MPR_JSON_COMBINE);

    blend("{numbers:[1,2]}", "{+numbers:[3,4]}", "{numbers:[1,2,3,4]}", MPR_JSON_COMBINE);
    blend("{numbers:[1,2]}", "{-numbers:[2,3]}", "{numbers:[1]}", MPR_JSON_COMBINE);
    blend("{numbers:[1,2]}", "{=numbers:[3]}", "{numbers:[3]}", MPR_JSON_COMBINE);
    blend("{numbers:[1,2]}", "{?numbers:[3]}", "{numbers:[1,2]}", MPR_JSON_COMBINE);

    blend("{user:{name:'john',age:30}}", "{}", "{user:{name:\"john\",age:30}}", MPR_JSON_COMBINE);
    blend("{user:{name:'john',age:30}}", "{user:{}}", "{user:{name:\"john\",age:30}}", MPR_JSON_COMBINE);
    blend("{user:{name:'john',age:30}}", "{-user:{}}", "{user:{name:\"john\",age:30}}", MPR_JSON_COMBINE);
    blend("{user:{name:'john',age:30}}", "{=user:{}}", "{user:{name:\"john\",age:30}}", MPR_JSON_COMBINE);
    blend("{user:{name:'john',age:30}}", "{?user:{}}", "{user:{name:\"john\",age:30}}", MPR_JSON_COMBINE);

    blend("{user:{name:'john',age:30}}", "{user:{+name: 'smith'}}", "{user:{name:\"john smith\",age:30}}", MPR_JSON_COMBINE);
    blend("{user:{name:'john',age:30}}", "{user:{-name: 'john'}}", "{user:{age:30}}", MPR_JSON_COMBINE);

    blend("{user:{}}", "{user:{?name: 'john'}}", "{user:{name:\"john\"}}", MPR_JSON_COMBINE);
    blend("{}", "{user:{?name: 'john'}}", "{user:{name:\"john\"}}", MPR_JSON_COMBINE);
}


static void jsonComments()
{
    MprJson     *obj;
    cchar       *s;

    obj = mprParseJson("/* Comment */");
    ttrue(obj == 0);

    obj = mprParseJson("// Rest of line ");
    ttrue(obj == 0);

    obj = mprParseJson("/* Leading Comment */ {}");
    ttrue(obj != 0);
    ttrue(obj->length == 0);

    obj = mprParseJson("{}/* Trailing Comment */");
    ttrue(obj != 0);
    ttrue(obj->length == 0);

    obj = mprParseJson("{/* Inside Comment */}");
    ttrue(obj != 0);
    ttrue(obj->length == 0);

    obj = mprParseJson("{/* Before name Comment */ color: 'red'}");
    ttrue(obj != 0);
    ttrue(obj->length == 1);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{color:\"red\"}"));

    obj = mprParseJson("{color: /* Before value */ 'red'}");
    ttrue(obj != 0);
    ttrue(obj->length == 1);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{color:\"red\"}"));

    obj = mprParseJson("{color: 'red' /* After value */}");
    ttrue(obj != 0);
    ttrue(obj->length == 1);
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{color:\"red\"}"));
}



static void jsonFuzz()
{
    /* All should fail */
    ttrue(!mprParseJson(0));
    ttrue(!mprParseJson(":::::"));
    ttrue(!mprParseJson("        "));
    ttrue(!mprParseJson("        \\"));
    ttrue(!mprParseJson("'"));
    ttrue(!mprParseJson("\\"));
    ttrue(!mprParseJson("\\a"));
    ttrue(!mprParseJson("\\{"));
    ttrue(!mprParseJson("{"));
    ttrue(!mprParseJson("["));
    ttrue(!mprParseJson("}"));
    ttrue(!mprParseJson("]"));
    ttrue(!mprParseJson("[[[[[[[[[["));
    ttrue(!mprParseJson("{{{{{{{{{{"));

    /* Should pass */
    ttrue(mprParseJson("abc") != 0);
    ttrue(mprParseJson("\x01") != 0);
    ttrue(mprParseJson("...") != 0);
    ttrue(mprParseJson("@") != 0);
}


static void jsonDuplicates()
{
    MprJson     *obj;
    cchar       *s;

    /*
        Last property wins
     */
    obj = mprParseJson("{color:\"red\", color:\"blue\"}");
    s = mprJsonToString(obj, 0);
    ttrue(smatch(s, "{color:\"blue\"}"));
}


static void jsonRegressions()
{
    MprJson *obj;
    char    *input, *s;

    /*
        Regressions
     */
    input = "{ \"key\": 42}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key\":42}"));

    input = "{ 'key': 42}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key\":42}"));

    input = "{ key: \"one \\\"two\"}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key\":\"one \\\"two\"}"));

    input = "{ 'key \"word': 42}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key \\\"word\":42}"));

    input = "{ 'key\r': 42}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key\\r\":42}"));

    input = "{ key: 'one\ntwo\n'}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"key\":\"one\\ntwo\\n\"}"));

    input = "{ name: 'Peter O\\'Tool'}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"name\":\"Peter O'Tool\"}"));

    input = "{ name: 'Peter O\\\"Tool'}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"name\":\"Peter O\\\"Tool\"}"));

    input = "{ name: 'Peter O\"Tool'}";
    obj = mprParseJson(input);
    ttrue(obj != 0);
    s = mprJsonToString(obj, MPR_JSON_QUOTES);
    ttrue(smatch(s, "{\"name\":\"Peter O\\\"Tool\"}"));
}


int main(int argc, char **argv)
{
    mprCreate(argc, argv, 0);
    jsonEmpty();
    jsonConstruct();
    jsonStrings();
    jsonTypes();
    jsonWhite();
    jsonReadWrite();
    jsonToString();
    jsonPretty();
    jsonElipsis();
    jsonQuery();
    jsonRemove();
    jsonSet();
    jsonBlend();
    jsonComments();
    jsonDuplicates();
    jsonFuzz();
    jsonSelectors();
    jsonRegressions();
    return 0;
}


/*
    @copy   default

    Copyright (c) Embedthis Software. All Rights Reserved.

    This software is distributed under commercial and open source licenses.
    You may use the Embedthis Open Source license or you may acquire a 
    commercial license from Embedthis Software. You agree to be fully bound
    by the terms of either license. Consult the LICENSE.md distributed with
    this software for full details and other copyrights.

    Local variables:
    tab-width: 4
    c-basic-offset: 4
    End:
    vim: sw=4 ts=4 expandtab

    @end
 */
